<template>
  <div class="container" v-loading="loading">
    <div style="width: 100%; height: 100%" id="graphic"></div>
    <el-empty style="position: absolute" v-if="!remoteLinks.length && !loading" description="暂无数据" />
  </div>
  <div class="tip-tool" v-if="remoteLinks.length > 0">
    <div class="v-box">
      <div style="width: 42px; height: 6px" :style="{ backgroundColor: palette.mediumgray }"></div>
      <p style="font-size: 12px; margin-top: 5px">攻击IP</p>
    </div>
    <div class="v-box mg-t-sm">
      <div style="width: 42px; height: 6px" :style="{ backgroundColor: palette.orange }"></div>
      <p style="font-size: 12px; margin-top: 5px">哨兵IP</p>
    </div>
    <div class="v-box mg-t-sm">
      <div style="width: 42px; height: 6px" :style="{ backgroundColor: config.searchColor }"></div>
      <p style="font-size: 12px; margin-top: 5px">检索IP</p>
    </div>
  </div>
  <div class="flot-tool" @click.stop>
    <div class="title" style="margin-left: 10px">{{ toolData.title }}</div>
    <div class="line" style="width: 95%; padding-bottom: 1px; background-color: #fff; margin: 2px auto 10px"></div>
    <div class="item">
      <span style="width: 90px; text-align: right; display: inline-block">攻击时间：</span>
      <span>{{ toolData.time }}</span>
    </div>
    <div class="item mg-t-sm">
      <span style="width: 90px; text-align: right; display: inline-block">攻击MAC：</span>
      <span>{{ toolData.mac }}</span>
    </div>
    <div class="item mg-t-sm">
      <span style="width: 90px; text-align: right; display: inline-block">攻击类型：</span>
      <span>{{ toolData.type }}</span>
    </div>
    <div class="item mg-t-sm">
      <span style="width: 90px; text-align: right; display: inline-block">攻击数量：</span>
      <span>{{ toolData.count }}</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import data from "./data.json";
import { getDataByPage as getLinkList } from "@/api/modules/attack-portrait/index";
// import { getDataByPage } from "@/api/modules/attack-portrait/index";
import { getDataByPage } from "@/api/modules/attack-traceability/attack-log";
import { ref, onMounted, reactive } from "vue";

interface Props {
  originIp?: string;
  type: number;
  searchData?: DataType;
}

const loading = ref(false);
const props = withDefaults(defineProps<Props>(), {
  originIp: "",
  type: 1,
  searchData: {}
});
const movePotision = reactive({
  clientX: 0,
  clientY: 0
});
const toolData = reactive({
  time: "",
  mac: "",
  type: "",
  count: 0
});
onMounted(() => {
  console.log(data);
  // console.log(props.originIp);
  toGetLinkList();
  document.onmousemove = event => {
    const { clientX, clientY } = event;
    // console.log(`鼠标X坐标: ${clientX}, 鼠标Y坐标: ${clientY}`);
    movePotision.clientX = clientX;
    movePotision.clientY = clientY;
  };

});

function elmentMouse(e) {
  console.log(e);
}
const show=ref(false)
const remoteLinks = ref([]);
const remoteData = ref([]);

async function toGetLinkList() {
  // if (!ip) {
  //   return;
  // }
  loading.value = true;
  try {
    let req = {
      curpage: 0,
      perpage: props.searchData.page_size === -1 ? 0 : props.searchData.page_size
    };

    const res = await getDataByPage(req);
    // console.log("res===========", res);
    // const res = {
    //   state: "success",
    //   format: "1",
    //   result: {
    //     total: 1042967,
    //     title: [
    //       "编号",
    //       "MAC地址",
    //       "源IP",
    //       "目的IP",
    //       "协议",
    //       "源端口",
    //       "目的端口",
    //       "流量流向",
    //       "属性",
    //       "上传流量",
    //       "下载流量",
    //       "开始时间",
    //       "持续时间",
    //       "详情"
    //     ],
    //     data: [
    //       [
    //         "1",
    //         "a0:36:9f:10:18:78",
    //         "192.168.3.144",
    //         "140.143.132.90",
    //         "TCP",
    //         "45266",
    //         "7000",
    //         "C类地址",
    //         "正常访问",
    //         "74B",
    //         "60B",
    //         "2025-08-31 09:26:07",
    //         "00:00:00",
    //         "/192.168.3.144/140.143.132.90/TCP/45266/7000/1756603567/1756603567"
    //       ],
    //       [
    //         "2",
    //         "00:0c:29:71:c2:3f",
    //         "192.168.3.135",
    //         "114.250.65.34",
    //         "TCP",
    //         "3715",
    //         "443",
    //         "C类地址",
    //         "正常访问",
    //         "60B",
    //         "0B",
    //         "2025-08-31 09:26:03",
    //         "00:00:00",
    //         "/192.168.3.135/114.250.65.34/TCP/3715/443/1756603563/1756603563"
    //       ],
    //       [
    //         "3",
    //         "a0:36:9f:10:18:78",
    //         "192.168.3.144",
    //         "140.143.132.90",
    //         "TCP",
    //         "36210",
    //         "7000",
    //         "C类地址",
    //         "正常访问",
    //         "74B",
    //         "60B",
    //         "2025-08-31 09:25:57",
    //         "00:00:00",
    //         "/192.168.3.144/140.143.132.90/TCP/36210/7000/1756603557/1756603557"
    //       ],
    //       [
    //         "4",
    //         "00:0c:29:25:27:f9",
    //         "192.168.3.115",
    //         "4.145.79.81",
    //         "TCP",
    //         "64791",
    //         "443",
    //         "C类地址",
    //         "正常访问",
    //         "60B",
    //         "66B",
    //         "2025-08-31 09:25:48",
    //         "00:00:00",
    //         "/192.168.3.115/4.145.79.81/TCP/64791/443/1756603548/1756603548"
    //       ],
    //       [
    //         "5",
    //         "a0:36:9f:10:18:78",
    //         "192.168.3.144",
    //         "140.143.132.90",
    //         "TCP",
    //         "53054",
    //         "7000",
    //         "C类地址",
    //         "正常访问",
    //         "74B",
    //         "60B",
    //         "2025-08-31 09:25:47",
    //         "00:00:00",
    //         "/192.168.3.144/140.143.132.90/TCP/53054/7000/1756603547/1756603547"
    //       ],
    //       [
    //         "6",
    //         "04:33:c2:05:48:08",
    //         "192.168.3.147",
    //         "223.5.5.5",
    //         "UDP",
    //         "64197",
    //         "53",
    //         "C类地址",
    //         "正常访问",
    //         "78B",
    //         "274B",
    //         "2025-08-31 09:25:45",
    //         "00:00:00",
    //         "/192.168.3.147/223.5.5.5/UDP/64197/53/1756603545/1756603545"
    //       ],
    //       [
    //         "7",
    //         "a0:36:9f:10:18:78",
    //         "192.168.3.144",
    //         "140.143.132.90",
    //         "TCP",
    //         "39352",
    //         "7000",
    //         "C类地址",
    //         "正常访问",
    //         "74B",
    //         "60B",
    //         "2025-08-31 09:25:37",
    //         "00:00:00",
    //         "/192.168.3.144/140.143.132.90/TCP/39352/7000/1756603537/1756603537"
    //       ],
    //       [
    //         "8",
    //         "a0:36:9f:10:18:78",
    //         "192.168.3.144",
    //         "140.143.132.90",
    //         "TCP",
    //         "40480",
    //         "7000",
    //         "C类地址",
    //         "正常访问",
    //         "74B",
    //         "60B",
    //         "2025-08-31 09:25:26",
    //         "00:00:00",
    //         "/192.168.3.144/140.143.132.90/TCP/40480/7000/1756603526/1756603526"
    //       ],
    //       [
    //         "9",
    //         "2c:db:07:ba:f2:25",
    //         "192.168.3.125",
    //         "192.168.31.200",
    //         "UDP",
    //         "61396",
    //         "161",
    //         "C类地址",
    //         "正常访问",
    //         "360B",
    //         "0B",
    //         "2025-08-31 09:25:26",
    //         "00:00:21",
    //         "/192.168.3.125/192.168.31.200/UDP/61396/161/1756603526/1756603547"
    //       ],
    //       [
    //         "10",
    //         "04:33:c2:05:48:08",
    //         "192.168.3.147",
    //         "223.5.5.5",
    //         "UDP",
    //         "64287",
    //         "53",
    //         "C类地址",
    //         "正常访问",
    //         "83B",
    //         "172B",
    //         "2025-08-31 09:25:21",
    //         "00:00:00",
    //         "/192.168.3.147/223.5.5.5/UDP/64287/53/1756603521/1756603521"
    //       ]
    //     ]
    //   }
    // };

    let data = res.result.data.map(item => {
      return {
        source: { name: item[2] },
        target: { name: item[3] }
      };
    });
    console.log("data", data);
    remoteLinks.value = data;
    remoteData.value = res.result.data;
    await getChartData(data);
    loading.value = false;
  } catch (error) {
    console.log(error);
    loading.value = false;
  }
}

const arrRemoval = list => {
  const set = new Set(list.map(JSON.stringify));

  const uniqueArr = Array.from(set).map(JSON.parse);

  return uniqueArr;
};
// 处理数据
const remoteNodes = ref([]);
const renderData = ref([]);

function getChartData(data: any) {
  //  1. 删除重复的数据
  const remoteData = arrRemoval(data);
  console.log("renderData.value", remoteData);
  renderData.value = remoteData;
  //  2. 获取实际的IP数量
  let actualIpData = [];
  remoteData.forEach(item => {
    actualIpData.push(item.source.name);
    actualIpData.push(item.target.name);
  });
  actualIpData = [...new Set(actualIpData)];
  // 3. 生成图表需要的数组
  console.log(actualIpData);
  let nodes = [];
  // let numNodes = 225;
  for (let x = 0; x < actualIpData.length; x++) {
    const arr = remoteData.filter(item => item.source.name === actualIpData[x]);
    const targetAry = arr.map(item => item.target.name.replaceAll(".", "_"));
    nodes.push({
      id: actualIpData[x].replaceAll(".", "_"),
      name: actualIpData[x],
      target: targetAry
    });
  }
  remoteNodes.value = nodes;
  draw();
  console.log(" remoteNodes.value", remoteNodes.value);
}

function debounce(func, wait) {
  let timeout;
  return function (...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

function mockData() {
  let nodes = [];
  let numNodes = 255;
  for (let x = 0; x < 255; x++) {
    let targetAry = [];
    let connections = Math.round(Math.random() * 5);
    for (let y = 0; y < connections; y++) {
      targetAry.push(Math.round(Math.random() * numNodes));
    }
    nodes.push({
      id: x,
      name: "192.168.0.1:" + x,
      target: targetAry
    });
  }
  console.log("nodes", nodes);
  let links = [];
  for (let i = 0; i < numNodes; i++) {
    if (nodes[i].target !== undefined) {
      for (let j = 0; j < nodes[i].target.length; j++) {
        console.log(nodes[i].target[j]);
        links.push({
          source: nodes[i],
          target: remoteNodes.value.find(item => item.name.replaceAll(".", "_") === nodes[i].target[j])
        });
      }
    }
  }
}

function clear() {
  d3.select("#graphic").selectAll("*").remove();

  // d3.select('svg').exit().remove();
}

const palette = {
  lightgray: "rgb(250,236,109)",
  gray: "rgb(241,238,238)",
  mediumgray: "rgb(22,23,23)",
  orange: "rgb(227,206,20)",
  purple: "rgb(48, 50, 63)",
  yellowgreen: "rgb(220,196,196)"
};
const config = {
  lineColor: "rgba(147,147,147,0.15)",
  lineHoverColor: palette.purple,
  textColor: "#222",
  searchColor: "#ed3a3a",
  textHoverSize: "30px",
  isSuspendedRepulsion: false // 是否悬浮排斥
};

function draw() {
  try {
    // Configure graphics
    let width = document.querySelector(".dialog-content.attack-portrait").clientWidth,
      height = document.querySelector(".dialog-content.attack-portrait").clientHeight;
    let circleWidth = 8,
      charge = -75,
      gravity = 0.05;

    // Generate test data
    let nodes = remoteNodes.value;
    let numNodes = remoteNodes.value.length;
    // mockData()
    // Create the links array from the generated data
    let links = [];
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].target !== undefined) {
        for (let j = 0; j < nodes[i].target.length; j++) {
          console.log(nodes[i].target[j]);
          links.push({
            source: nodes[i],
            target: remoteNodes.value.find(item => item.name.replaceAll(".", "_") === nodes[i].target[j])
          });
        }
      }
    }
    console.log("links", links);
    // Create SVG

    let svg = d3
      .select("#graphic")
      .append("svg")

      .attr("width", width)
      .attr("height", height);
    let fdGraph = svg.append("g").attr("class", "group");
    // .attr("transform", "translate(" + 2.639015821545792 + ")scale(" + [-2063.157367805762, -405.64719552365057] + ")");
    // svg.call(window.d3.behavior.zoom().on("zoom", zoomed));
    const zoom = window.d3.behavior.zoom().scaleExtent([0.5, 15]).on("zoom", zoomed);
    svg.call(zoom);
    // 初始缩放比例
    const initialScale = 2; // 设置初始缩放比例
    const initialTranslate = [0 - width / 2, 0 - height / 2]; // 设置初始平移

    // 应用初始变换
    fdGraph.attr("transform", `translate(${initialTranslate[0]}, ${initialTranslate[1]}) scale(${initialScale})`);
    // 设置默认缩放比例
    // const defaultScale = 2; // 设置默认放大比例
    // svg.transition().call(window.d3.behavior.zoom().scaleBy, defaultScale); // 应用默认缩放比例
    function zoomed() {
      const flotToolDom = document.querySelector(".flot-tool");
      flotToolDom.style.display = "none";
      console.log("zoomed", window.d3.event.scale); //2.639015821545792
      console.log("zoomed", window.d3.event.translate); //[-2063.157367805762, -405.64719552365057]
      fdGraph.attr("transform", "translate(" + window.d3.event.translate + ")scale(" + window.d3.event.scale + ")");
      if (window.d3.event.scale < 1) {
        d3.selectAll(".label-text").style("opacity", 0);
      } else {
        d3.selectAll(".label-text").style("opacity", 1);
      }
    }

    // Create the force layout to calculate and animate node spacing
    let forceLayout = d3.layout
      .force()

      .nodes(nodes)
      .links([])
      .gravity(gravity)
      .charge(charge)
      .linkDistance(180)
      .size([width, height]);
    // 定义箭头的形状
    fdGraph
      .append("defs")
      .selectAll("marker")
      .data(["end"]) // 使用 marker 的名称
      .enter()
      .append("marker")
      .attr("id", "arrow")
      .attr("viewBox", "0 0 10 10")
      .attr("refX", 20)
      .attr("refY", 5) // 箭头的 y 坐标
      .attr("markerWidth", 6)
      .attr("markerHeight", 6)
      .attr("orient", "auto")
      .append("polygon")
      .attr("points", "0,0 10,5 0,10") // 箭头的形状
      .attr("class", "arrow")
      .attr("fill", config.lineHoverColor);
    // Create the SVG lines for the links
    let link = fdGraph
      .selectAll("line")
      .data(links)
      .enter()
      .append("line")
      .attr("stroke", config.lineColor)
      .attr("stroke-width", 1)
      .attr("x1", d => (d.source + 1) * 200 + circleWidth) // 线条的起点 x 坐标 (外部)
      .attr("y1", 300) // 线条的起点 y 坐标
      .attr("x2", d => (d.target + 1) * 200 - circleWidth) // 线条的终点 x 坐标 (外部)
      .attr("y2", 300) // 线条的终点 y 坐标
      .attr("class", function (d, i) {
        // Add classes to lines to identify their from's and to's
        let theClass = "line_" + d.source.id + " line";
        if (d.target !== undefined) {
          theClass += " to_" + d.target.id;
        }
        return theClass;
      });
    let timeoutId;

    // Create the SVG groups for the nodes and their labels
    function mouseleave(d, i) {
      const flotToolDom = document.querySelector(".flot-tool");
      flotToolDom.style.display = "none";
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
      d3.selectAll(".node").style("filter", "opacity(1)");
      d3.selectAll(".line")
        // .attr("stroke", palette.lightgray)
        .attr("stroke", config.lineColor)
        .style("filter", "opacity(1)")
        .attr("marker-end", ""); // 添加箭头
      d3.selectAll(".label-text")
        .attr("font-size", "10")
        .attr("font-weight", "lighter")
        .attr("fill", config.textColor)
        .style("transform", "scale(0.5) translateY(30px)")
        .attr("text-anchor", "middle"); // 文本居中对齐
    }

    let node = fdGraph
      .selectAll("circle")
      .data(nodes)
      .enter()
      .append("g")
      .attr("id", function (d) {
        return "node_" + d.id;
      })
      .attr("tag", function (d) {
        return d.name;
      })
      .attr("class", "node")
      .on("mouseleave", (d, i) => mouseleave(d, i))
      .on("click", (d, i) => {
        if (d.target?.length) {
          console.log("准备", d);
          console.log("remoteData.value", remoteData.value);
          let arr = [];
          remoteData.value.forEach((item, index) => {
            if (item[2] === d.name && arr.length <= 5) {
              arr.push({
                mac: item[1],
                time: item[7],
                count: d.target?.length,
                title: d.name,
                type: item[4]
              });
            }
          });
          console.log(arr);
          if (arr.length > 0) {
            toolData.mac = arr[0].mac;
            toolData.time = arr[0].time;
            toolData.count = arr[0].count;
            toolData.title = arr[0].title;
            let types = arr.map(item => item.type);
            types = [...new Set(types)];
            if (types.length > 3) {
              toolData.type = types.join(", ") + `...`;
            } else {
              toolData.type = types.join(", ");
            }
            const flotToolDom = document.querySelector(".flot-tool");
            flotToolDom.style.display = "block";
            flotToolDom.style.left = movePotision.clientX + "px";
            flotToolDom.style.top = movePotision.clientY + "px";
          }
        }
      })
      .on("mouseover", function (d) {
        timeoutId = setTimeout(async () => {
          console.log("d", d);
          // When mousing over a node, make the label bigger and bold
          // and revert any previously enlarged text to normal
          d3.selectAll(".node").style("filter", "opacity(0.4)");
          d3.selectAll(".node").selectAll("text").attr("font-size", "10").attr("font-weight", "normal");

          // Highlight the current node

          d3.select(this)
            .select("text")
            .style("opacity", 1)
            .attr("font-size", config.textHoverSize)
            .style("transform", "scale(0.98) translateY(22px)")
            .style("filter", "opacity(1)");
          d3.select(`#node_${d.id}`).style("filter", "opacity(1)");
          // Hightlight the nodes that the current node connects to
          for (let i = 0; i < d.target.length; i++) {
            d3.select("#node_" + d.target[i])
              .style("filter", "opacity(1)")
              .select("text")
              .attr("font-size", config.textHoverSize)
              .style("opacity", 1)
              .style("transform", "scale(0.98) translateY(22px)")
              .style("filter", "opacity(1)");
          }

          // Reset and fade-out the unrelated links
          d3.selectAll(".line")
            // .attr("stroke", palette.lightgray)
            .attr("stroke", config.lineColor)
            .attr("stroke-width", 1)
            .style("filter", "opacity(1)");

          for (let x = 0; x < links.length; x++) {
            if (links[x].target !== undefined) {
              if (links[x].target.id === d.id) {
                // Highlight the connections to this node
                d3.selectAll(".to_" + links[x].target.id)
                  // .attr("stroke", palette.orange)
                  .attr("stroke", config.lineColor)
                  .attr("stroke-width", 1);

                // Highlight the nodes connected to this one
                d3.select("#node_" + links[x].source.id)
                  .select("text")
                  .attr("font-size", "10");
              }
            }
          }

          // Highlight the connections from this node
          d3.selectAll(".line_" + d.id)
            // .attr("stroke", palette.purple)
            .attr("stroke", config.lineHoverColor)
            .attr("stroke-width", 1)
            .attr("marker-end", "url(#arrow)"); // 添加箭头

          // When mousing over a node,
          // make it more repulsive so it stands out
          if (config.isSuspendedRepulsion) {
            forceLayout.charge(function (d2, i) {
              if (d2 != d) {
                // Make the nodes connected to more repulsive
                for (let i = 0; i < d.target.length; i++) {
                  if (d2.id == d.target[i]) {
                    return charge * 8;
                  }
                }

                // Make the nodes connected from more repulsive
                for (let x = 0; x < links.length; x++) {
                  if (links[x].source.id === d2.id) {
                    if (links[x].target !== undefined) {
                      if (links[x].target.id === d.id) {
                        return charge * 8;
                      }
                    }
                  }
                }

                // Reset unrelated nodes
                return charge;
              } else {
                // Make the selected node more repulsive
                return charge * 10;
              }
            });
          }

          forceLayout.start();
        }, 200);
        if (d.target?.length) {
          console.log("准备", d);
          console.log("remoteData.value", remoteData.value);
          let arr = [];
          remoteData.value.forEach((item, index) => {
            if (item[2] === d.name && arr.length <= 5) {
              arr.push({
                mac: item[1],
                time: item[7],
                count: d.target?.length,
                title: d.name,
                type: item[4]
              });
            }
          });
          console.log(arr);
          if (arr.length > 0) {
            toolData.mac = arr[0].mac;
            toolData.time = arr[0].time;
            toolData.count = arr[0].count;
            toolData.title = arr[0].title;
            let types = arr.map(item => item.type);
            types = [...new Set(types)];
            if (types.length > 3) {
              toolData.type = types.join(", ") + `...`;
            } else {
              toolData.type = types.join(", ");
            }
            const flotToolDom = document.querySelector(".flot-tool");
            flotToolDom.style.display = "block";
            flotToolDom.style.left = movePotision.clientX + "px";
            flotToolDom.style.top = movePotision.clientY + "px";
          }
        }
      })
      .call(forceLayout.drag);

    // Create the SVG circles for the nodes
    node
      .append("circle")
      .attr("cx", function (d) {
        return d.x;
      })
      .attr("cy", function (d) {
        return d.y;
      })
      .attr("r", circleWidth)
      .attr("fill", function (d, i) {
        console.log("d", d);
        // Color 1/3 of the nodes each color
        // Depending on the data, this can be made more meaningful
        if (d.target?.length) {
          return palette.purple;
        } else {
          return palette.orange;
        }
        // return palette.yellowgreen;
      });

    // Create the SVG text to label the nodes
    node
      .append("text")
      .text(function (d) {
        return d.name;
      })
      .attr("class", "label-text")
      .attr("font-size", "10")
      .attr("font-weight", "lighter")
      .attr("fill", config.textColor)
      .attr("x", d => d.x) // 设置文本 x 坐标
      .attr("y", d => {
        return d.y0;
      }) // 将 y 坐标向下移动 20 像素
      .attr("text-anchor", "middle"); // 文本居中对齐

    // Animate the layout every time tick
    forceLayout.on("tick", function (e) {
      // Move the nodes base on charge and gravity
      node.attr("transform", function (d, i) {
        return "translate(" + d.x + ", " + d.y + ")";
      });
      // Adjust the lines to the new node positions
      link
        .attr("x1", function (d) {
          return d.source.x;
        })
        .attr("y1", function (d) {
          return d.source.y;
        })
        .attr("x2", function (d) {
          if (d.target !== undefined) {
            return d.target.x;
          } else {
            return d.source.x;
          }
        })
        .attr("y2", function (d) {
          if (d.target !== undefined) {
            return d.target.y;
          } else {
            return d.source.y;
          }
        });
    });

    // Start the initial layout
    forceLayout.start();
  } catch (e) {
    alert(e);
  }
}

const curSearchElement = ref(null);

function searchIp(ip) {
  const searchElement = d3.selectAll(`[tag*="${ip}"]`);
  const searchNotElement = d3.selectAll(`.node:not([tag*="${ip}"])`);
  console.log("searchElement::", searchElement);
  console.log("searchNotElement::", searchNotElement);
  if (searchElement.size()) {
    searchElement.selectAll("circle").style("fill", config.searchColor);
    searchElement.selectAll(".label-text").style("fill", config.searchColor);
  }
  if (searchNotElement.size()) {
    searchNotElement.selectAll("circle").style("fill", function (d, i) {
      console.log("d", d);
      if (d.target?.length) {
        return palette.purple;
      } else {
        return palette.orange;
      }
    });
    searchNotElement.selectAll(".label-text").style("fill", config.textColor);
  }
}

defineExpose({
  toGetLinkList,
  clear,
  searchIp
});
</script>
<!--
1. 画布范围限定
2. 画圆点，随机分布，鼠标悬浮高亮
3. 画圆点旁边的文字
4. 画连线
5. 随机颜色
-->
<style>
.container {
  width: 100%;
  height: 100%;
  max-width: 100vw;
  max-height: 100vh;
  position: relative;
  display: flex;
  justify-content: center;
}

.container svg {
  width: 100%;
  height: 100%;
  cursor: pointer;
}

.label-text {
  transition: 0.3s ease-in-out;
  transform: scale(0.5) translateY(30px);
  font-size: 10px;
}

.node,
.line {
  //transition: 0.3s ease-in-out;
}

.tip-tool {
  position: fixed;
  right: 120px;
  top: 320px;
}

.flot-tool {
  padding: 10px;
  border-radius: 10px;
  background-color: rgba(48, 46, 46, 0.85);
  position: fixed;
  top: 0px;
  left: 450px;
  display: none;

  .title {
    font-size: 16px;
    color: #fff;
    line-height: 35px;
  }

  .item {
    color: #fff;

    span {
      &:nth-child(1) {
      }
    }
  }
}
</style>

<!--https://codepen.io/darrengriffith/pen/KpKxqR
https://codepen.io/thepeted/pen/MyNEeP
-->
